/**
 *	The NeuroCoSA Toolkit
 *	Copyright (C) 2003-6 Stuart Meikle.
 *
 *	This is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU Lesser General Public
 *	License as published by the Free Software Foundation; either
 *	version 2.1 of the License, or (at your option) any later version.
 *
 *	This library is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *	Lesser General Public License for more details.
 *
 * @author	Stuart Meikle
 * @version	2006-halloween(mk2)
 * @license	LGPL
 */
package org.stumeikle.NeuroCoSA;

// a very simple neuron 
// stumeikle 20040606
// stumeikle 20041112 updated post introduction of bimodeneuron.
// stumeikle 20051009 updated, including (trying to) expectation and surprise.

import org.stumeikle.NeuroCoSA.NIS.*;
import java.util.*;

import java.awt.*;
import java.awt.event.*;
import java.lang.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
import javax.swing.event.*;

/**
 * A very simple neuron. Can be used as a reference implementation for 
 * further neuron development .
 * Simple here means: respects the feedback and drive control mechanisms, as
 * defined in simple synapse etc. Simple from a neuron perspective as the winning
 * learn or drive signal takes control of the neuron. inputs are not summed and weighted. 
 * output is always a learn or drive vesicle
 */
public class	SimpleNeuron	extends BiModeNeuron implements SimpleBackPropAPI
{
    private	Object		iData;	/** arbitrary iData stored here, eg centroid info */
    protected	Vesicle		iVessyL,/** output vesicle when mode is learning */
    				iVessyD;/** output vesicle when mode is driving */
    protected   Vesicle		iOPVesicle;
    private	Synapse		iDriveController; /** synpase which drives the strongest */
    private	int		iDriveStatus; /** has our drive been able to control the body */
    public  static final int	drive_status_undefined=-1;
    public  static final int	drive_status_waiting=0; /** drive status value */
    public  static final int	drive_status_accepted=1;/** drive status value */
    public  static final int	drive_status_rejected=2;/** drive status value */
    private	int		iDriveSuccess;
    public  static final int	drive_success_undefined=-1;
    public  static final int	drive_succeeded=1; /** drive success value, used by synapse */
    public  static final int	drive_failed=2;/** drive success value, used by synapse */
    private	int		iLastMode;/** what (bi-)mode did we have last time we updated?*/
    private 	boolean		iProbeSynapsesPresent; /** if we have any probe synapses this should be true */
    private	boolean		iDrivesMustCompete; /** do incoming drives need to compete to get control 
						    *  of this neuron ? default = true /  yes*/
    
    //------------------- Constructors ------------------------------
    public SimpleNeuron(Layer l)
    { super(l);  allocVP(); }
    
    /** allocate the vesicle packages */
    private	void		allocVP()
    {
        iVessyL = new Vesicle( Vesicle.LEARN_VESICLE );
	iVessyD = new Vesicle( Vesicle.DRIVE_VESICLE );
	iData    = null;
	iDriveStatus = drive_status_undefined;
	iDriveSuccess= drive_success_undefined;
	iProbeSynapsesPresent = false;
	iDriveController = null;
	iOPVesicle = null;
	iDrivesMustCompete = true;
    }
    
    public	void		setData( Object d ) 
    { iData = d; }
    public	Object		getData()
    { return iData; }

    public	boolean		drivesMustCompete()
    { return iDrivesMustCompete; }
	
    public	void		setDrivesMustCompete( boolean b )
    { iDrivesMustCompete = b; }
    
    public	Synapse		getDriveController() throws DriveControllerIrrelevantException
    {
	if (iDrivesMustCompete)
	    return iDriveController;
	
	throw new DriveControllerIrrelevantException();
    }
    
    public	void		setProbeSynapsesPresent()
    { iProbeSynapsesPresent = true; }
    
    public	void		acceptProbeSynapse( ProbeSynapse s )
    {
	setProbeSynapsesPresent();
	connectOutput(s);
    }

    //------------------ the required services ------------------------
    /** a required method implementation . 
     *  Scan the neuron's inputs and form a vesicle package. Sum the signal strengths or
     * otherwise determine a new signal strength. Also process commands given by the lobe.
     * @param vt   vesicle received, signal strength can be zero 
     * @return the created vesicle pacakge
     */
    protected	Vesicle 	createBiModeVesicle(Vesicle vt)
    {
       	//scan the inputs and fill in the vesicle
    	ListIterator	i;
	double		ss=0.0;
	iOPVesicle = null;

    	iVessyL.setSignalStrength(0.0);
	iVessyD.setSignalStrength(0.0);
	iDriveController=null; //stumeikle 20060205
	
	//(ZING	experimental 20070311)
	startOfVesicleProcessing();

	//(1)process lobe commands first
	processVesicle( vt, null );

	//(2)process probe synapse inputs second
	// 20070311 -> due to new design of probe synapses, no longer needed
	//---------------------------------------------------------------------------------------
	
	//(3) process inputs next
	i=iInputs.listIterator(0);
	for(;i.hasNext();)
        {
	    Synapse	s=(Synapse)i.next();
	    Vesicle     v2= s.getOutputVesicle();

	    processVesicle( v2, s );
        }

	//(ZING	experimental 20070311)
	endOfVesicleProcessing();
	
	//we have to create the vesicle package here again :-(
	ss = setModeGetSigStr();
	if (getMode()==mode_driving)
	    iOPVesicle = iVessyD ;
	else
	    iOPVesicle =  iVessyL ;
	iOPVesicle.setSignalStrength(ss);
	
	//do expectation actions
	if (getMode()==mode_driving && iLastMode!=mode_driving)
	{
	    //start of driving request mode
	    //System.out.println("Neuron " + this + " setting drive status=waiting. " + 
	    //			"Last mode=" + iLastMode);
	    iDriveStatus=drive_status_waiting;
	    iDriveSuccess=drive_success_undefined;
	}
	if (getMode()!=mode_driving)
	{
	    //20051009 trial...????? see if this is ok
	    //this means we stop waiting on responses once we are no longer
	    //driving.
	    //System.out.println("Neuron " + this + " setting drive status=undefined");
	    iDriveStatus=drive_status_undefined;
	}
	
	iLastMode= getMode();
        
	return iOPVesicle;
    }
    
    /** also overload the new updateVesiclePackage method. primarily used for 
     *  inhibition...
     */
    protected	void  		updateVesicle(Vesicle vt)
    {
    	if (getMode()==mode_driving && vt.getType()==Vesicle.DRIVE_VESICLE)
	{
	    iOPVesicle.setSignalStrength( iOPVesicle.getSignalStrength() + vt.getSignalStrength());
	}
    }    

    //*******************************************************************************
    /** protected method, called only by the createVesiclePackage implementation 
     * of this class (well, kinda)
     */ 
    public    void		processVesicle( Vesicle v, Synapse s )
    {
        if (v!=null)
	{       
	    switch( v.getType())
	    {
		//incoming vesicle is a learn vesicle.
		//set pointer according to the winner in the output lobe
	        case Vesicle.LEARN_VESICLE:
		        processLearn( v , s );
			break;
		case Vesicle.DRIVE_VESICLE:
			processDrive( v , s );
			break;		        
		case Vesicle.SPECIAL_VESICLE:
			processSpecial( v, s );
			break;
		default:
			;
	    }
	}
    }

    public	void		processSpecial( Vesicle v, Synapse s )
    {
	try
	{
	    ProbeVesicle	pv = (ProbeVesicle)v;
	    
	    //probe vesicle passes both learn AND drive signals
	    pv.setLearnMode();
	    processLearn( pv, s );
	    pv.setDriveMode();
	    processDrive( pv, s );
	}catch(Exception e){}
    }

    /** protected method, called only by the processVesicle implementation 
     * of this class. but could be overridden by derived classes
     */
    public	void		processLearn( Vesicle v, Synapse s )
    {
	simpleUpdateLearnSig( v.getSignalStrength(), s );
    }
    
    /** protected method, called only by the processVesicle implementation 
     * of this class. but could be overridden by derived classes
     */
    public	void		processDrive( Vesicle v, Synapse s )
    {
	simpleUpdateDriveSig(v.getSignalStrength(),s);
    }

    /**
     * New method. Stumeikle 20060613. Not yet added to neuronAPI.
     * register this neuron for callback during backpropagation.
     * Only required for neurons which do not have synaptic outputs
     */
    public	void		registerForBackPropCallback()
    {
	//add the neuron to the list of back prop entities in SimpleBrain.
	SimpleBrain.registerNeuron( this );	
    }

    /**
     * Stumeikle 20060613. remove backprop call backs
     */
    public	void		deRegisterForBackPropCallback()
    {
	SimpleBrain.deregisterNeuron(this);
    }
    
    /** (New method, stumeikle 20060613). SimpleBackPropAPI
     */
    public	void		backPropagate()
    {
    }	        

    //************************************************************************************
    
    //====================================================================================
    //these are now the gateways to the bimode neuron learn/drive values
    //all simpleneuron and derived methods must come through here.

    //------------------------------------------------------------------------------------
    // Routines which are final here and should not be overloaded. The previous 5 methods
    // Should all come via these routines
    //------------------------------------------------------------------------------------
    public final boolean	simpleUpdateDriveSig( double d, Synapse s )
    {
System.out.println("DEBUG CONTROLLER: entering simple Update Drive Sig. this=" + this + " drive strength=" + d );
    	if (super.updateDriveSig(d)==true)
    	{
	    if (drivesMustCompete())
	    {
    	        iDriveController = s;
System.out.println("DEBUG CONTROLLER: " + this + " setting contrller to " + s );
    	        return true;
	    }
    	}
    	
    	return false;
    }
    
    public final boolean	simpleUpdateLearnSig( double d, Synapse s )
    {
    	return super.updateLearnSig(d);
    }
    
    //Catch routines to make sure that no simple - things are trying to call the 
    //base class methods
    public void			setLearnSigStr( double s )
    {
    	System.out.println("SIMPLE XXX CALLING BIMODE SIGSTR ROUTINE DIRECTLY");
    	System.out.println("User must use simple neuron methods instead");
    	System.out.println("Calling class is " + this );
    	System.exit(-1);
    }
    public void			setDriveSigStr( double s )
    {
    	System.out.println("SIMPLE XXX CALLING BIMODE SIGSTR ROUTINE DIRECTLY");
    	System.out.println("User must use simple neuron methods instead");
    	System.out.println("Calling class is " + this );
    	System.exit(-1);
    }
    public boolean		updateLearnSig( double d )
    {
   	System.out.println("SIMPLE XXX CALLING BIMODE SIGSTR ROUTINE DIRECTLY");
    	System.out.println("User must use simple neuron methods instead");
    	System.out.println("Calling class is " + this );
    	System.exit(-1);
	return false;
    }
    public boolean		updateDriveSig( double d )
    {
   	System.out.println("SIMPLE XXX CALLING BIMODE SIGSTR ROUTINE DIRECTLY");
    	System.out.println("User must use simple neuron methods instead");
    	System.out.println("Calling class is " + this );
    	System.exit(-1);
	return false;
    }
    //====================================================================================== 
   
   
    /** a required method implementation . 
     * Check the vesicle package. Have all contained vesicles been propagated ?
     * If not (or otherwise) create new connetions to neurons in the output lobes. 
     */
    public	void		updateConnections(Vesicle v)
    {
    	//if synapses removed , can we say why and create new connections accordingly ?
	//eg if the expectation time needs to change ot be faster/ slower etc, or multivalued
    }
    
    /** 
     * Called by neuron. Here we check with the synapses to see if they passed a drive
     * signal and if it was successful in beeing the winning drive signal of the other lobe
     * updated stumeikle 20051012. We know now if we drove the output via DriveStatus
     * var. We need to check if driving was successful, signalled by us changing from 
     * drive to learn. 
     */
    protected	void		reviewBehaviour()
    {
    	// algo:
    	//did we drive last time ?
	//(successfully)
	//are we learning now ?
	//	yes -> success
	//	no -> not yet success
	//	no, and we're not excited -> failure 

	//possible problem here. driving the current neuron will yeild success without action which 
	//in turn will generate surprise
	LayerInfoService	l = getLayer().getInfoService();
	InfoWithSingleStore	winning_drive, winning_learn;

	winning_drive = (InfoWithSingleStore) l.findInfo("WinningDrive");
	winning_learn = (InfoWithSingleStore) l.findInfo("WinningLearn");
	if (winning_drive !=null && winning_learn !=null)
	{
	    if ((BiModeNeuron) winning_drive.getValue(Info.last_update_time) == this  && 
		(BiModeNeuron) winning_learn.getValue(Info.last_update_time) == this )
	    {
		if (iDriveStatus == drive_status_accepted)
	    	{
		    iDriveSuccess = drive_succeeded;
	        }
	    }
	}
	
	if (iLastMode==mode_driving && iDriveStatus == drive_status_accepted )
	{
	    int		currmode = getMode();
	    
	    //previously was currmode == mode_learning
	    //but that's not correct either. what we want is 
	    
	    /* new method 
	    //if we were the learn winner last time (as review behaviour happens before the update
	    //) then -> Success. Limit firing to once only by saving , if drive succes !=succeeded
	    //if ( //iDriveSuccess != drive_succeeded && //only respond once
	    //     getLobe().getWinnerAtTimeT( Brain.getTime() -1 , Vesicle.LEARN_VESICLE)==this )
	    */
	    
	    //arg. depends a bit on the lobe we're talking about. 
	    //delta will switch from drive -ve or +ve to drive zero. so success will be 
	    //hard to judge there. might need to stimulate success at the lobe level
	    //action map though , we should be able to say 'success' but , so what, if
	    //that cant be propagated to the synapses, neurons of delta lobe . arg.

	    if ( //iDriveSuccess != drive_succeeded && //only respond once
	        winning_learn != null )
	    {
		if ((BiModeNeuron)winning_learn.getValue(Info.last_update_time) == this )
		{ 

	    	    //success...
		    iDriveSuccess = drive_succeeded;

		    //synapses will be updated as normal, due to this neuron being excited
		}else
	    	{
	    	    if (getState()==Neuron.state_excited)
		    {
		        //ok, keep trying
		    }else
		    {
		        //failure ...
		    	iDriveSuccess = drive_failed;
		    	//synapses won't be updated by the neuron, so we will do it here
		    	//, so that they can process the failure. The alternative would be 
		    	//to always update the synapses
		    	updateSynapses(null); // this also resets the synapses , but this is fine
		    			  // in this case. 
		    }
	    	}
	    }
	}
  
	//call the review behaviour function of the simple neurons
	simpleReviewBehaviour();
    }
    
    /** needs to be overloaded
     */
    public	void		simpleReviewBehaviour()
    {}

    /** can be overloaded
     */
    public	void		startOfVesicleProcessing()
    {}
 
    public	void		endOfVesicleProcessing()
    {}

    //----------------------------------------------------------------------------------
    /** Expectation routines */
    public	void		notifyDriveAccepted()
    {
    	/** called by the synapse or other entity to indicate that the requested
	 *  drive did achieve control of the body
	 */
	//don't question it in this case
	iDriveStatus = drive_status_accepted;
	
	//System.out.println("SimpleNeuron " + this + " drive accepted." );
	//updating the LIS should occur automatically...
    }
    
    public	void		notifyDriveNotAccepted()
    {
    	/** called by the synapse or other entity to indicate that the requested
	 *  drive did not achieve control of the body (or was interrupted)
	 */
	//don't question it in this case
	iDriveStatus = drive_status_rejected;
	
	//updating the LIS should occur automatically...
	//drive was rejected (or not yet successful). 
	//all we need to do is pass the info up the chain
	//our controller will stop driving us.
    }
//not good.............
    public	void		notifyDriveUndefined()
    {
	iDriveStatus = drive_status_undefined;
    }
    public	void		notifyDriveWaiting()
    {
	iDriveStatus = drive_status_waiting; 
    }
    
    public	int		getDriveStatus()
    {	return iDriveStatus; }
    
    public	int		getDriveSuccess()
    { 	return iDriveSuccess; }
    
    public	void		setDriveSuccess( int s )
    {
    	iDriveSuccess = s;
	//needed occasionally
    }
}
